-
Notifications
You must be signed in to change notification settings - Fork 270
Set __module__ on curried objects. This can fix pickling global curried objects
#355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ried objects. Should fix dask/distributed#725 This was an oversight that should have been handled in pytoolz#326. We only tested objects defined in `toolz.functoolz`, so pickling accidentally worked, because they were in the same module as `curry`.
|
Thanks for fixing this |
|
Sure thing, my bad for the oversight. Time for a bug-fix release? |
|
|
||
| self.__doc__ = getattr(func, '__doc__', None) | ||
| self.__name__ = getattr(func, '__name__', '<curry>') | ||
| self.__module__ = getattr(func, '__module__', None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not use functools.wraps or functools.update_wrapper? See https://docs.python.org/2.7/library/functools.html#functools.update_wrapper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been bitten by those functions before, but I suppose we were bitten by not using them since __module__ wasn't being set but should have.
Mostly, I want control over how curry behaves, and being explicit makes it easier for us to implement and test cytoolz.curry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then you probably want to copy __qualname__ on Python 3 as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want #335. This was also explored and discussed in #230. My bad for letting things linger.
Things can get convoluted, though, so I prefer straightforward and explicit even if it's a little more verbose. For example, __annotations__ will need to be custom-handled for curry much like curry.__signature__.
To your point, though, I'll go ahead and add __qualname__ to curry in Python 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If available, is __qualname__ used during pickling? If so, I'd like to test this behavior in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIR, it is. It should allow pickling class methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the critical eye and expert knowledge, @pitrou! More tests coming...
This isn't pretty, but hopefully tests provide adequate coverage. This includes a signficant change to `curry`: we defined `__getattr__` to get attributes from the wrapped func.
|
Okay, we now take advantage of For more robust serialization support, |
My intuition would be to only copy a well-known set of attributes rather than forward all of them. |
|
At a minimum, we can choose not to forward dunder attributes. Forwarding other attributes allows non-curried attributes with Learning how to be a good Python citizen w.r.t.
One final comment regarding |
|
I think there may be a Python enhancement to learn from this experience: when pickling objects with |
…affects. IMHO, this is a failure of how Python handles `__qualname__` during pickling. Objects wrapped by decorators should only need to use `__wrapped__` for pickling to work as expected. It's unreasonable to expect wrapping objects such as `curry` to forward attributes to allow pickling to work.
|
I removed I'll look into enhancing Python to better support pickling decorated objects. Unfortunately, |
The common way is to use |
|
@pitrou, I know that. They don't help here, although I would be delighted if you could correct me. Here's an example of what #326 and this PR are meant to resolve (ignoring Python 3.5.2 |Continuum Analytics, Inc.| (default, Jul 2 2016, 17:52:12)
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import functools
>>> import pickle
>>>
>>> class decorator:
... def __init__(self, func):
... self.__wrapped__ = func
... functools.update_wrapper(self, func)
... def __call__(self, *args, **kwargs):
... print('calling decorated function')
... return self.__wrapped__(*args, **kwargs)
...
>>> @decorator
... def f(x):
... return x
...
>>> f(1)
calling decorated function
1
>>> pickle.dumps(f, -1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function f at 0x101b79c80>: it's not the same object as __main__.fIt's common--and convenient--for We support what we must support, as shown below, but it would be great if serialization were more robust. Python 3.5.2 |Continuum Analytics, Inc.| (default, Jul 2 2016, 17:52:12)
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> from toolz import curry
>>>
>>> @curry
... def f(x, y):
... return x + y
...
>>> pickle.loads(pickle.dumps(f)) is f
True
>>>
>>> f1 = f(1)
>>> f1_new = pickle.loads(pickle.dumps(f1))
>>> f1(2) == f1_new(2) == 3
TruePickling that leverages |
Also, fix signature registry for flip and memoize. I had forgotten that we can list multiple signatures.
Fix serializing global curried objects. pytoolz/toolz#355
Should fix dask/distributed#725
This was an oversight that should have been handled in #326. We only tested objects defined in
toolz.functoolz, so pickling accidentally worked, because they were in the same module ascurry.